/**
 * This file is part of WiiuseJ.
 *
 *  WiiuseJ is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  WiiuseJ is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with WiiuseJ.  If not, see <http://www.gnu.org/licenses/>.
 */
package wiiusej;

import java.util.HashMap;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import wiiusej.connection.ApiConnection;
import wiiusej.wiiusejevents.utils.EventsGatherer;

/**
 * Class that manages the use of Wiiuse API.
 * 
 * @author guiguito
 */
public class WiiUseApiManager implements ApiConnection{

	//Logger 
	private static Logger fileLogger;
	
	//Packet counter
	private long packetCounter = 0;
	
	/*
	//Queue for COM with Wiimotes
	BlockingQueue<HashMap<Integer, BlockingQueue<GenericEvent>>> fromConnection;
	BlockingQueue<WiiUseApiEvent> toConnection;
	
	private static HashMap<Integer, BlockingQueue<GenericEvent>> wiimoteComChannels;*/
	
	//myID
	private static long myID;
	public static String myName;
	
	private static WiiUseApiManager instance;// = new WiiUseApiManager();

	//private final EventListenerList listeners = new EventListenerList();

	

	//private Wiimote[] wiimotes;
	
	//private static HashMap<Integer, Wiimote> wiimotes;

	private WiiUseApi wiiuse = WiiUseApi.getInstance();

	private int connected = 0;

	//private AtomicBoolean running = new AtomicBoolean(false);

	private boolean leave = false;

	//public static int WIIUSE_STACK_UNKNOWN = 0;
	//public static int WIIUSE_STACK_MS = 1;
	//public static int WIIUSE_STACK_BLUESOLEIL = 2;

	//connectionManager
	//private static ConnectionManager connectionManager;
	
	//the barrier
	//private final CyclicBarrier barrier;
	
	public WiiUseApiManager(){
		
		instance = this;
		
		//Logger init
		PropertyConfigurator.configure("log4j.properties");
		fileLogger = Logger.getLogger("wiiuseManager");
		
		
			
	}
	
	
	public static WiiUseApiManager getInstance() {
		return instance;
	}
	
	/**
	 * Get the number of wiimotes connected.
	 * 
	 * @return the number of wiimotes connected.
	 */
	public static int getNbConnectedWiimotes() {
		return getInstance().connected;
	}

//	/**
//	 * Stop thread and shutdown wiiuse Api.
//	 */
//	
//	
//	public static void shutdown() {
//		
//		WiiUseApiManager manager = getInstance();
//		int pastConnected = manager.connected; 
//		
//		
//		/*
//		if(!wiimotes.isEmpty()){
//			
//			Iterator it = wiimotes.entrySet().iterator();		
//			
//			while(it.hasNext()){
//				Map.Entry entry = (Map.Entry) it.next();
//				Wiimote wim = (Wiimote) entry.getValue();
//				
//				wim.disconnect();
//			}			
//		}*/
//		
//		manager.running.set(false);
//		
//		if (pastConnected > 0) {
//			manager.wiiuse.cleanUp();
//		}
//		
//		/*
//		if (manager.connected > 0) {
//			for (Wiimote wim : manager.wiimotes) {
//				if (wim != null)
//					wim.disconnect();
//			}
//		}
//		manager.running.set(false);
//		if (pastConnected > 0) {
//			manager.wiiuse.cleanUp();
//		}*/
//	}

	/**
	 * Stop wiiuseJ definitively for this program. It finishes Wiiusej thread
	 * and shutdown wiiuse API.
	 */
	/*
	public static void definitiveShutdown() {
		getInstance().leave = true;
		shutdown();
	}*/

	
	
	/**
	 * Activate the rumble for the wiimote with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void activateRumble(int id) {
		wiiuse.activateRumble(id);
	}

	/**
	 * Deactivate the rumble for the wiimote with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void deactivateRumble(int id) {
		wiiuse.deactivateRumble(id);
	}

	/**
	 * Activate IR Tracking for the wiimote with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void activateIRTRacking(int id) {
		wiiuse.activateIRTracking(id);
	}

	/**
	 * Deactivate IR Tracking for the wiimote with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void deactivateIRTRacking(int id) {
		wiiuse.deactivateIRTracking(id);
	}

	/**
	 * Activate motion sensing for the wiimote with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void activateMotionSensing(int id) {
		wiiuse.activateMotionSensing(id);
	}

	/**
	 * Deactivate motion sensing for the wiimoter with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void deactivateMotionSensing(int id) {
		wiiuse.deactivateMotionSensing(id);
	}

	/**
	 * Activate smoothing the wiimotes with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void activateSmoothing(int id) {
		wiiuse.activateSmoothing(id);
	}

	/**
	 * Deactivate smoothing the wiimotes with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void deactivateSmoothing(int id) {
		wiiuse.deactivateSmoothing(id);
	}

	/**
	 * Activate continuous for the wiimotes with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void activateContinuous(int id) {
		wiiuse.activateContinuous(id);
	}

	/**
	 * Deactivate continuous for the wiimotes with the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void deactivateContinuous(int id) {
		wiiuse.deactivateContinuous(id);
	}

	
	/**
	 * Set leds for the wiimotes with the given id.
	 * 
	 * @param id
	 *            id of the wiimote
	 * @param l1
	 *            status of led1. True : ON, False : OFF.
	 * @param l2
	 *            status of led2. True : ON, False : OFF.
	 * @param l3
	 *            status of led3. True : ON, False : OFF.
	 * @param l4
	 *            status of led4. True : ON, False : OFF.
	 */
	protected void setLeds(int id, boolean l1, boolean l2, boolean l3,
			boolean l4) {
		wiiuse.setLeds(id, l1, l2, l3, l4);
	}

	/**
	 * Set the orientation threshold for the given id. (minimum angle between
	 * two events)
	 * 
	 * @param id
	 *            id of the wiimote.
	 * @param th
	 *            threshold in degrees.
	 */
	protected void setOrientationThreshold(int id, float th) {
		wiiuse.setOrientThreshold(id, th);
	}

	/**
	 * Set the acceleration threshold for the given id. (minimum angle between
	 * two events)
	 * 
	 * @param id
	 *            id of the wiimote.
	 * @param th
	 *            threshold.
	 */
	protected void setAccelerationThreshold(int id, int th) {
		wiiuse.setAccelThreshold(id, th);
	}

	/**
	 * Set alpha smoothing for the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 * @param th
	 *            threshold.
	 */
	protected void setAlphaSmoothing(int id, float th) {
		wiiuse.setAlphaSmoothing(id, th);
	}

	/**
	 * Try to resync with the wiimote by starting a new handshake.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void reSync(int id) {
		wiiuse.reSync(id);
	}

	/**
	 * Set screen aspect ratio to 4/3 for the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void setScreenAspectRatio43(int id) {
		wiiuse.setScreenRatio43(id);
	}

	/**
	 * Set screen aspect ratio to 16/9 for the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void setScreenAspectRatio169(int id) {
		wiiuse.setScreenRatio169(id);
	}

	/**
	 * Set the sensor bar to be above the screen.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void setSensorBarAboveScreen(int id) {
		wiiuse.setSensorBarAboveScreen(id);
	}

	/**
	 * Set the sensor bar to be below the screen.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void setSensorBarBelowScreen(int id) {
		wiiuse.setSensorBarBelowScreen(id);
	}

	/**
	 * Set virtual resolution. It is used to automatically compute the position
	 * of a cursor on this virtual screen using the sensor bar. These results
	 * come in the IREvent.
	 * 
	 * @param id
	 *            id of the wiimote.
	 * @param x
	 *            x resolution.
	 * @param y
	 *            y resolution.
	 */
	protected void setVirtualResolution(int id, int x, int y) {
		wiiuse.setVirtualScreenResolution(id, x, y);
	}

	/**
	 * Get Status for the wiimote for the given id.
	 * 
	 * @param id
	 *            id of the wiimote.
	 */
	protected void getStatus(int id) {
		wiiuse.getStatus(id);
	}

	/**
	 * Set the normal and expansion handshake timeouts.
	 * 
	 * @param id
	 *            the id of the wiimote concerned.
	 * @param normalTimeout
	 *            The timeout in milliseconds for a normal read.
	 * @param expansionTimeout
	 *            The timeout in millisecondsd to wait for an expansion
	 *            handshake.
	 */
	protected void setTimeout(int id, short normalTimeout,
			short expansionTimeout) {
		wiiuse.setTimeout(id, normalTimeout, expansionTimeout);
	}

	/**
	 * Set the IR sensitivity.
	 * 
	 * @param id
	 *            the id of the wiimote concerned.
	 * @param level
	 *            1-5, same as Wii system sensitivity setting. If the level is <
	 *            1, then level will be set to 1. If the level is > 5, then
	 *            level will be set to 5.
	 */
	protected void setIrSensitivity(int id, int level) {
		wiiuse.setIrSensitivity(id, level);
	}

	/**
	 * Set the nunchuk orientation threshold for the given id. (minimum angle
	 * between two events)
	 * 
	 * @param id
	 *            id of the wiimote.
	 * @param th
	 *            threshold in degrees.
	 */
	protected void setNunchukOrientationThreshold(int id, float th) {
		wiiuse.setNunchukOrientationThreshold(id, th);
	}

	/**
	 * Set the nunchuk acceleration threshold for the given id. (minimum angle
	 * between two events)
	 * 
	 * @param id
	 *            id of the wiimote.
	 * @param th
	 *            threshold.
	 */
	protected void setNunchukAccelerationThreshold(int id, int th) {
		wiiuse.setNunchukAccelerationThreshold(id, th);
	}

	

	
	/*
	@Override
	public void run() {

		fileLogger.debug("Waiting for the barrier");
				
		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SS");
		
		//listen the blocking queue from ConnectionManager
		try {
			wiimoteComChannels = fromConnection.take();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		fileLogger.debug("Received Message from ConnectionManger - Wiimotes: " 
				+ wiimoteComChannels.size() + " Time: " + tsFormat.format(System.currentTimeMillis()));	
		
		this.connected = wiimoteComChannels.size();
		
		fileLogger.debug("Found Connected Channels: " + wiimoteComChannels + 
				"Connected: " + this.connected);
		
		running.set(true);		
		
		EventsGatherer gather = new EventsGatherer(connected);	
			
		//Discard Events which have arrived before the wake-up process
		wiiuse.specialPoll(gather);
		gather.clearEvents();
		
		fileLogger.debug("");
		fileLogger.debug("*******************************");
		fileLogger.debug("EVENTS GATHERED BEFORE INIT APP: " + gather.getEvents().length);
		fileLogger.debug("*******************************");
		fileLogger.debug("");			
		
		//Request for Wiimote Status
		wiiuse.getStatus(11);		
		
		while (running.get() && !wiimoteComChannels.isEmpty()) {
						
			//Check if there were changes in the wiimote connection
			
			HashMap<Integer, Wiimote> uploadedWiimotes;
			
			//EVENT POLLING
			wiiuse.specialPoll(gather);
						
			//deal with events gathered in Wiiuse API
			for (WiiUseApiEvent evt : gather.getEvents()) {
				
				if (evt.getWiimoteId() != -1) {// event filled
					
					
					switch(evt.getEventType()){
					
					case WiiUseApiEvent.DISCONNECTION_EVENT:
						
						fileLogger.info("");
						fileLogger.info("DISCONNECTION EVENT RECEIVED");
						fileLogger.info("");
						
						try {
							toConnection.put(evt);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						
						break;
						
					case WiiUseApiEvent.STATUS_EVENT:
						
						StatusEvent status = (StatusEvent) evt;
						fileLogger.info("");
						fileLogger.info(status);
						fileLogger.info("");
						
						break;
					
					default: 
						notifyWiiUseApiListener(evt);
						break;
					}					
					
				} else {
					fileLogger.error("NON-VALID WIIMOTE ID");
				}
			}
			gather.clearEvents();
		}		
		
	}
	
*/
	
	/**
	 * Add WiiUseApiListener to the listeners list.
	 * 
	 * @param listener
	 *            a WiiUseApiListener
	 */
	
	/*public void addWiiUseApiListener(WiiUseApiListener listener) {
		listeners.add(WiiUseApiListener.class, listener);
		fileLogger.debug("ConnectedBefore: " + this.connected);
		this.connected++;
		fileLogger.debug("ConnectedAfter: " + this.connected);
	}*/
	
	/*
	private void removeSetOfWiimotes(HashMap<Integer, Wiimote> wiimotes){
		
		Iterator it = wiimotes.entrySet().iterator();		
		
		while(it.hasNext()){
			Map.Entry entry = (Map.Entry) it.next();
			Wiimote wim = (Wiimote) entry.getValue();			
			removeWiimote(wim);
		}
		
	}*/

	/*
	private void removeWiimote(Wiimote wim) {
		this.wiimotes.remove(wim);		
	}*/

	/*
	private void removeSetOfWiiUseApiListeners(HashMap<Integer, Wiimote> listeners){
		
		Iterator it = listeners.entrySet().iterator();		
		
		while(it.hasNext()){
			Map.Entry entry = (Map.Entry) it.next();
			Wiimote wim = (Wiimote) entry.getValue();			
			removeWiiUseApiListener(wim);
		}
		
	}*/
	
	
	
	/**
	 * Remove WiiUseApiListener from the listeners list.
	 * 
	 * @param listener
	 *            a WiiUseApiListener
	 */
	/*protected void removeWiiUseApiListener(WiiUseApiListener listener) {
		listeners.remove(WiiUseApiListener.class, listener);
		this.connected--;
	}*/

	/**
	 * Get the list of WiiUseApiListeners.
	 * 
	 * @return the list of WiiUseApiListeners.
	 */
	/*protected WiiUseApiListener[] getWiiUseApiListeners() {
		return listeners.getListeners(WiiUseApiListener.class);
	}*/

	
	/*
	private void notifyWiiUseApiListener(WiiUseApiEvent evt) {
		
		
		if (evt.getEventType() == WiiUseApiEvent.GENERIC_EVENT) {
			
			HashMap<String, Timestamp> timeStamp;		
			Timestamp ts = new Timestamp (System.currentTimeMillis());
			//timeStamp = ((WiimoteEvent) evt).getMotionSensingEvent().getTimeStamp();
			
			timeStamp = new HashMap<String, Timestamp>();
			timeStamp.put(myName,ts);
			
			((WiimoteEvent) evt).getMotionSensingEvent().setTimeStamp(timeStamp);
			((WiimoteEvent) evt).getMotionSensingEvent().setTime(ts);
			
			((WiimoteEvent) evt).setTime(ts);
			
			//set refNumber			
			packetCounter++;
			evt.setRefNumber(packetCounter);
		}
		
		BlockingQueue<GenericEvent> q = wiimoteComChannels.get(evt.getWiimoteId());
		
		if( q !=  null){
			//the data channel exists
			
			if((evt instanceof WiimoteEvent)){
				fileLogger.info("Packet: " + evt.getRefNumber() + 
						" Sending Event To: " + evt.getWiimoteId() +
						" Time: " + ((WiimoteEvent) evt).getTime());
				}
			
			try {				
				q.put(evt);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
			
		}else{
			fileLogger.info("ERROR finding comunucation channel. Wiimote: " + evt.getWiimoteId() + 
					" does NOT exists");
		}		
		
	}*/
	
	

	/**
	 * Called by the garbage collector at the end.
	 */
	/*
	protected void finalize() throws Throwable {
		shutdown();
	}*/
	
	
	/***************************************************
	MOD2: SPEAKER BASIC METHODS
	***************************************************/
	protected void activateSpeaker(int id){
		wiiuse.activateSpeaker(id);
	}
	
	protected void sendSound(int id){
		wiiuse.sendSound(id);		
	}
	
	protected void deactivateSpeaker(int id){
		wiiuse.deactivateSpeaker(id);		
	}
	
	/***************************************************
	MOD3: SPEAKER VOLUME METHODS
	***************************************************/
	
	protected int turnVolumeUp(int id){
		return wiiuse.turnVolumeUp(id);
	}
	
	protected int turnVolumeDown(int id){
		return wiiuse.turnVolumeDown(id);
	}
	
	protected int getVolumeLevel(int id){
		return wiiuse.getVolumeLevel(id);
	}
	
	/***************************************************
	MOD4: SPEAKER CONFIG METHODS
	***************************************************/

	protected void activateSpeaker(int id, int config, byte[] sampleRate){
		wiiuse.activateSpeaker(id, config, sampleRate);
	}
	
	
	/***************************************************
	API CONECTION INTERFACE IMPLEMENTATION
	***************************************************/
	
	/**
	 * Force the bluetooth stack type.(useful only for windows)
	 * 
	 * @param type
	 *            must be WIIUSE_STACK_UNKNOWN or WIIUSE_STACK_MS or
	 *            WIIUSE_STACK_BLUESOLEIL.
	 */
	
	@Override
	public void setBlueToothstackType(int type) {
		wiiuse.windowsSetBluetoothStack(type);
	}  
	
	
	@Override
	public void initDataStructure(int nbPossibleWiimotes) {
		wiiuse.init(nbPossibleWiimotes);
		
	}

	@Override
	public int findWiimotes(int nbMaxWiimotes, int timeout) {		
		return wiiuse.find(nbMaxWiimotes, timeout);
	}

	@Override
	public int connectDiscoveredWiimotes(int nbWiimotesFound) {		
		return wiiuse.connect(nbWiimotesFound);
	}

	@Override
	public int getUnId(int i) {
		return wiiuse.getUnId(i);
	}
	
	
	public void closeSetOfConnections(HashMap<Integer, Wiimote> toDelete){
		
	}
	
	public void closeConnection(int id) {
		
	}
	

	/***************************************************
	CONTROLLER LEFT METHODS
	***************************************************/
	

	public void specialPoll(EventsGatherer gather) {
		wiiuse.specialPoll(gather);		
	}


	

	
	
}

